package com.redhipps.hips.client.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public class AutoCompleteController<T> {

  private PopupPanel choicesPopup;
  private ListBox choicesListBox;
  private Map<String, T> completionItems;

  private CompletionFormatter<T> formatter;

  public AutoCompleteController(Map<String, T> completionItems, CompletionFormatter<T> formatter) {
    this.completionItems = completionItems;
    this.formatter = formatter;
    this.choicesPopup = new PopupPanel(true);
    this.choicesListBox = new ListBox();
    choicesPopup.add(choicesListBox);
  }

  public Widget wrap(TextBox target, CompletionListener<T> completionListener) {
    new SingleCompletionController(target, completionListener);
    return target;
  }
  
  private class SingleCompletionController {

    private CompletionListener<T> completionListener;
    private TextBox target;
    private AutoCompleteChangeListener changeListener;
    private AutoCompleteKeyListener keyListener;
    private List<T> currentValues;

    public SingleCompletionController(TextBox target, CompletionListener<T> completionListener) {
      this.target = target;
      this.completionListener = completionListener;
      keyListener = new AutoCompleteKeyListener();
      changeListener = new AutoCompleteChangeListener();
      target.addKeyboardListener(keyListener);
    }

    private void complete() {
      if (currentValues.size() > 1) {
        int choice = choicesListBox.getSelectedIndex();
        T value = currentValues.get(choice);
        String formatted = formatter.format(value);
        target.setText(formatted);
        completionListener.valueCompleted(formatted, value);
      } else if (currentValues.size() == 1) {
        T value = currentValues.get(0);
        String formatted = formatter.format(value);
        completionListener.valueCompleted(formatted, value);
      }
      choicesListBox.clear();
      choicesPopup.hide();
    }

    private void show() {
      choicesListBox.setWidth(target.getOffsetWidth() + "px");
      choicesListBox.addChangeListener(changeListener);
      choicesPopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
        public void setPosition(int offsetWidth, int offsetHeight) {
          choicesPopup.setPopupPosition(target.getAbsoluteLeft(), 
              target.getAbsoluteTop() + target.getOffsetHeight());
        }
      });
    }

    private void hide() {
      choicesPopup.hide();
      choicesListBox.clear();
      choicesListBox.removeChangeListener(changeListener);
      currentValues = null;
    }

    private class AutoCompleteKeyListener extends KeyboardListenerAdapter {
  
      @Override
      public void onKeyUp(Widget sender, char keyCode, int modifiers) {
        switch (keyCode) {
          case KEY_DOWN: {
            int selectedIndex = choicesListBox.getSelectedIndex();
            selectedIndex++;
            if (selectedIndex > choicesListBox.getItemCount()) {
              selectedIndex = 0;
            }
            choicesListBox.setSelectedIndex(selectedIndex);
            return;
          }
          case KEY_UP: {
            int selectedIndex = choicesListBox.getSelectedIndex();
            selectedIndex--;
            if (selectedIndex < 0) {
              selectedIndex = choicesListBox.getItemCount();
            }
            choicesListBox.setSelectedIndex(selectedIndex);
            return;
          }
          case KEY_ENTER:
            complete();
            return;
          case KEY_ESCAPE:
            hide();
            return;
        }
  
        String text = target.getText().toLowerCase();
        List<String> matches = new ArrayList<String>();
        currentValues = new ArrayList<T>();
        if (text.length() > 0) {
          for (Map.Entry<String, T> e : completionItems.entrySet()) {
            if (e.getKey().startsWith(text) && !currentValues.contains(e.getValue())) {
              String formattedValue = formatter.format(e.getValue());
              matches.add(formattedValue);
              currentValues.add(e.getValue());
            }
          }
        }
  
        if (!matches.isEmpty()) {
          choicesListBox.clear();
          // if there is only one match and it is what is in the
          // text field anyways there is no need to show autocompletion
          if (matches.size() == 1 && matches.get(0).equals(text)) {
            choicesPopup.hide();
          } else {
            for (int i = 0; i < matches.size(); i++) {
              choicesListBox.addItem(matches.get(i));
            }
            choicesListBox.setSelectedIndex(0);
            choicesListBox.setVisibleItemCount(matches.size() + 1);
            show();
          }
  
        } else {
          hide();
        }
      }
  
    }

    // This is invoked when something is selected from the list.
    private class AutoCompleteChangeListener implements ChangeListener {
  
      public void onChange(Widget sender) {
        // TODO This doesn't seem to work.
        complete();
      }
  
    }
  }
}
